package com.phiture.erp.purchase.web.controller.admin.api;

import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.phiture.erp.basic.api.pojo.resp.ErpMaterialInfoResp;
import com.phiture.erp.basic.api.pojo.resp.ErpUserCustomizeConfigResp;
import com.phiture.erp.basic.api.service.ErpMaterialApi;
import com.phiture.erp.basic.api.service.ErpUserCustomizeConfigApi;
import com.phiture.erp.common.constants.ErpUserCustomizeConfigKeyConstants;
import com.phiture.erp.common.enums.ErpAuditStatus;
import com.phiture.erp.common.enums.ErpPurchaseDemandTypeEnum;
import com.phiture.erp.common.utils.ErpNoGenerator;
import com.phiture.erp.purchase.api.pojo.ErpPurchaseRequestItemUpdate;
import com.phiture.erp.purchase.api.pojo.req.CreatePurchaseRequestFromMrpReq;
import com.phiture.erp.purchase.api.pojo.resp.ErpPurchaseRequestItemsResp;
import com.phiture.erp.purchase.api.service.ErpPurchaseRequestApi;
import com.phiture.erp.purchase.core.domain.ErpPurchaseConfig;
import com.phiture.erp.purchase.core.pojo.dto.CreatePurchaseOrderMergeEntryDTO;
import com.phiture.erp.purchase.core.pojo.dto.ErpPurchaseOrderDTO;
import com.phiture.erp.purchase.core.pojo.dto.ErpPurchaseOrderItemDTO;
import com.phiture.erp.purchase.core.service.ErpPurchaseOrderMergeEntryService;
import com.phiture.erp.purchase.core.service.ErpPurchaseOrderService;
import com.phiture.erp.purchase.core.service.ErpPurchaseRequestService;
import com.phiture.erp.purchase.dal.entity.ErpPurchaseOrderDO;
import com.phiture.erp.purchase.dal.entity.ErpPurchaseOrderItemDO;
import com.phiture.erp.purchase.dal.entity.ErpPurchaseRequestDO;
import com.phiture.erp.purchase.dal.entity.ErpPurchaseRequestItemsDO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.phiture.erp.common.constants.ErpNoPrefixConstants.PURCHASE_ORDER_NO_PREFIX;
import static com.phiture.erp.common.constants.ErpNoPrefixConstants.PUR_REQUEST_NO_PREFIX;

@Service
@RequiredArgsConstructor
public class ErpPurchaseRequestApiServiceImpl implements ErpPurchaseRequestApi {
    private final ErpPurchaseRequestService purchaseRequestService;
    private final ErpUserCustomizeConfigApi erpUserCustomizeConfigApi;
    private final ErpMaterialApi erpMaterialApi;
    private final ErpNoGenerator erpNoGenerator;
    private final ErpPurchaseOrderService purchaseOrderService;
    private final ErpPurchaseOrderMergeEntryService purchaseOrderMergeEntryService;

    private static final int HARVEST_LEAD_TIME = 7;
    private static final List<String> CONFIG_KEYS = Arrays.asList(
            ErpUserCustomizeConfigKeyConstants.ERP_PURCHASE_REQUEST_DEFAULT_APPROVED,
            ErpUserCustomizeConfigKeyConstants.ERP_AUTO_CREATE_PURCHASE_ORDER,
            ErpUserCustomizeConfigKeyConstants.ERP_MERGE_PURCHASE_ORDERS
    );

    @Override
    public List<ErpPurchaseRequestItemsResp> getPurchaseRequestItemsListByRequestIds(List<Long> requestIds) {

        return null;
    }

    @Override
    public ErpPurchaseRequestItemsResp getPurchaseRequestItemsListById(Long id) {
        ErpPurchaseRequestItemsDO result = purchaseRequestService.getPurchaseRequestItemsListBId(id);
        return BeanUtils.toBean(result, ErpPurchaseRequestItemsResp.class);
    }

    @Override
    public int updatePurchaseRequestItem(ErpPurchaseRequestItemUpdate item) {
        return purchaseRequestService.updatePurchaseRequestItem(BeanUtils.toBean(item, ErpPurchaseRequestItemsDO.class));
    }

    @Override
    public void createPurchaseRequestFromMrp(List<CreatePurchaseRequestFromMrpReq> list, boolean batchMode) {
        // 获取物料信息
        Map<Long, ErpMaterialInfoResp> materialMap = getMaterialMap(list);
        // 采购配置
        ErpPurchaseConfig purchaseConfig = loadPurchaseConfig(batchMode);

        LocalDateTime orderTime = LocalDateTime.now();
        Map<String, ErpPurchaseOrderDTO> purchaseOrderMap = new HashMap<>(list.size());
        Set<CreatePurchaseOrderMergeEntryDTO> entrySet = new HashSet<>(list.size());
        list.forEach(item -> {
            ErpMaterialInfoResp material = materialMap.get(item.getMaterialId());
            ErpPurchaseRequestItemsDO itemsDO = handlePurchaseRequest(material, purchaseConfig, item);
            if (purchaseConfig.getAllowCreatePurchaseOrder()) {
                if (purchaseConfig.getAllowMergePurchaseOrder() && material.getSupplierId() != null) {
                    String mapKey = String.format("%s_%s", material.getId(), material.getSupplierId());
                    if (purchaseOrderMap.containsKey(mapKey)) {
                        ErpPurchaseOrderDTO orderDTO = purchaseOrderMap.get(mapKey);
                        ErpPurchaseOrderItemDTO dtoItem = orderDTO.getItem();
                        entrySet.add(buildCreatePurchaseOrderMergeEntryDTO(orderDTO.getNo(), orderDTO.getRequestId(), orderDTO.getRequestItemId(), material.getId(), orderDTO.getTotalCount()));
                        switch (purchaseConfig.getMergeTypeEnum()) {
                            case THE_SAME_TIME:
                                if (dtoItem.getDeliveryDate().isEqual(itemsDO.getRequireDate())) {
                                    orderDTO.setTotalCount(orderDTO.getTotalCount().add(itemsDO.getQty()));
                                    orderDTO.setTotalPrice(orderDTO.getTotalPrice().add(itemsDO.getAmount()));
                                    orderDTO.setTotalTaxPrice(orderDTO.getTotalTaxPrice().add(itemsDO.getTaxAmount()));
                                    dtoItem.setCount(dtoItem.getCount().add(itemsDO.getQty()));
                                    dtoItem.setTotalPrice(orderDTO.getTotalPrice());
                                    orderDTO.setItem(dtoItem);
                                    entrySet.add(buildCreatePurchaseOrderMergeEntryDTO(orderDTO.getNo(), itemsDO.getRequestId(), itemsDO.getId(), material.getId(), itemsDO.getQty()));
                                } else {
                                    purchaseOrderMap.put(mapKey, buildErpPurchaseOrderDTO(material, itemsDO, orderTime));
                                }
                                break;
                            case THE_EARLIEST_TIME:
                                orderDTO.setTotalCount(orderDTO.getTotalCount().add(itemsDO.getQty()));
                                orderDTO.setTotalPrice(orderDTO.getTotalPrice().add(itemsDO.getAmount()));
                                orderDTO.setTotalTaxPrice(orderDTO.getTotalTaxPrice().add(itemsDO.getTaxAmount()));
                                dtoItem.setCount(dtoItem.getCount().add(itemsDO.getQty()));
                                dtoItem.setTotalPrice(orderDTO.getTotalPrice());
                                dtoItem.setDeliveryDate(
                                        dtoItem.getDeliveryDate().isBefore(itemsDO.getRequireDate())
                                                ? dtoItem.getDeliveryDate() : itemsDO.getRequireDate()
                                );
                                orderDTO.setItem(dtoItem);
                                entrySet.add(buildCreatePurchaseOrderMergeEntryDTO(orderDTO.getNo(), itemsDO.getRequestId(), itemsDO.getId(), material.getId(), itemsDO.getQty()));
                                break;
                            case THE_LATEST_TIME:
                                orderDTO.setTotalCount(orderDTO.getTotalCount().add(itemsDO.getQty()));
                                orderDTO.setTotalPrice(orderDTO.getTotalPrice().add(itemsDO.getAmount()));
                                orderDTO.setTotalTaxPrice(orderDTO.getTotalTaxPrice().add(itemsDO.getTaxAmount()));
                                dtoItem.setCount(dtoItem.getCount().add(itemsDO.getQty()));
                                dtoItem.setTotalPrice(orderDTO.getTotalPrice());
                                dtoItem.setDeliveryDate(
                                        dtoItem.getDeliveryDate().isAfter(itemsDO.getRequireDate())
                                                ? dtoItem.getDeliveryDate() : itemsDO.getRequireDate()
                                );
                                orderDTO.setItem(dtoItem);
                                entrySet.add(buildCreatePurchaseOrderMergeEntryDTO(orderDTO.getNo(), itemsDO.getRequestId(), itemsDO.getId(), material.getId(), itemsDO.getQty()));
                                break;
                        }
                    } else {
                        purchaseOrderMap.put(mapKey, buildErpPurchaseOrderDTO(material, itemsDO, orderTime));
                    }
                } else {
                    ErpPurchaseOrderDO orderDO = processPurchaseOrder(material.getSupplierId(), itemsDO, orderTime);
                    processPurchaseOrderItem(orderDO.getId(), orderDO.getTotalPrice(), material, itemsDO);
                }
            }
        });

        if(batchMode && MapUtil.isNotEmpty(purchaseOrderMap)){
            List<ErpPurchaseOrderDTO> dtoList = new ArrayList<>(purchaseOrderMap.values());
            for (ErpPurchaseOrderDTO orderDTO : dtoList) {
                Long orderId = saveMergeErpPurchaseOrderDO(orderDTO);
                saveMergeErpPurchaseOrderItemDO(orderId, orderDTO.getItem());
            }
            purchaseOrderMergeEntryService.batchCreatePurchaseOrderMergeEntry(entrySet);
        }
    }

    private Long saveMergeErpPurchaseOrderDO(ErpPurchaseOrderDTO orderDTO){
        ErpPurchaseOrderDO orderDO = new ErpPurchaseOrderDO();
        orderDO.setNo(orderDTO.getNo());
        orderDO.setStatus(orderDTO.getStatus());
        orderDO.setSupplierId(orderDTO.getSupplierId());
        orderDO.setOrderTime(orderDTO.getOrderTime());
        orderDO.setTotalCount(orderDTO.getTotalCount());
        orderDO.setTotalPrice(orderDTO.getTotalPrice());
        orderDO.setTotalTaxPrice(orderDTO.getTotalTaxPrice());
        orderDO.setTotalMaterialPrice(orderDTO.getTotalMaterialPrice());
        orderDO.setInCount(orderDTO.getInCount());
        orderDO.setReturnCount(orderDTO.getReturnCount());
        orderDO.setMerged(orderDTO.getMerged());
        orderDO.setCreator(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        orderDO.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        purchaseOrderService.createErpPurchaseOrder(orderDO);
        return orderDO.getId();
    }

    private void saveMergeErpPurchaseOrderItemDO(Long orderId, ErpPurchaseOrderItemDTO itemDTO){
        ErpPurchaseOrderItemDO itemDO = new ErpPurchaseOrderItemDO();
        itemDO.setOrderId(orderId);
        itemDO.setMaterialId(itemDTO.getMaterialId());
        itemDO.setMaterialUnitId(itemDTO.getMaterialUnitId());
        itemDO.setMaterialPrice(itemDTO.getMaterialPrice());
        itemDO.setCount(itemDTO.getCount());
        itemDO.setTotalPrice(itemDTO.getTotalPrice());
        itemDO.setInCount(itemDTO.getInCount());
        itemDO.setReturnCount(itemDTO.getReturnCount());
        itemDO.setDeliveryDate(itemDTO.getDeliveryDate());
        itemDO.setCreator(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        itemDO.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        purchaseOrderService.createErpPurchaseOrderItem(itemDO);
    }

    private ErpPurchaseOrderDTO buildErpPurchaseOrderDTO(ErpMaterialInfoResp material, ErpPurchaseRequestItemsDO itemsDO, LocalDateTime orderTime) {
        ErpPurchaseOrderDTO orderDTO = new ErpPurchaseOrderDTO();
        orderDTO.setRequestId(itemsDO.getRequestId());
        orderDTO.setRequestItemId(itemsDO.getId());
        orderDTO.setNo(erpNoGenerator.generate(PURCHASE_ORDER_NO_PREFIX));
        orderDTO.setStatus(ErpAuditStatus.PROCESS.getStatus());
        orderDTO.setSupplierId(material.getSupplierId());
        orderDTO.setOrderTime(orderTime);
        orderDTO.setTotalCount(itemsDO.getQty());
        orderDTO.setTotalPrice(itemsDO.getAmount());
        orderDTO.setTotalMaterialPrice(itemsDO.getAmount());
        orderDTO.setTotalTaxPrice(itemsDO.getTaxAmount());
        orderDTO.setInCount(BigDecimal.ZERO);
        orderDTO.setReturnCount(BigDecimal.ZERO);
        orderDTO.setMerged(Boolean.TRUE);

        ErpPurchaseOrderItemDTO itemDTO = new ErpPurchaseOrderItemDTO();
        itemDTO.setMaterialId(material.getId());
        itemDTO.setMaterialUnitId(material.getUnitId());
        itemDTO.setMaterialPrice(material.getPurchasePrice());
        itemDTO.setCount(itemsDO.getQty());
        itemDTO.setTotalPrice(orderDTO.getTotalPrice());
        itemDTO.setInCount(BigDecimal.ZERO);
        itemDTO.setReturnCount(BigDecimal.ZERO);
        itemDTO.setSourceBillId(itemsDO.getRequestId());
        itemDTO.setSourceBillDetailId(itemsDO.getId());
        itemDTO.setDeliveryDate(itemsDO.getRequireDate());
        orderDTO.setItem(itemDTO);
        return orderDTO;
    }

    private void processPurchaseOrderItem(Long orderId, BigDecimal totalPrice, ErpMaterialInfoResp material, ErpPurchaseRequestItemsDO itemsDO) {
        ErpPurchaseOrderItemDO orderItemDO = new ErpPurchaseOrderItemDO();
        orderItemDO.setOrderId(orderId);
        orderItemDO.setMaterialId(material.getId());
        orderItemDO.setMaterialUnitId(material.getUnitId());
        orderItemDO.setMaterialPrice(material.getPurchasePrice());
        orderItemDO.setCount(itemsDO.getQty());
        orderItemDO.setTotalPrice(totalPrice);
        orderItemDO.setInCount(BigDecimal.ZERO);
        orderItemDO.setReturnCount(BigDecimal.ZERO);
        orderItemDO.setSourceBillId(itemsDO.getRequestId());
        orderItemDO.setSourceBillDetailId(itemsDO.getId());
        orderItemDO.setDeliveryDate(itemsDO.getRequireDate());
        orderItemDO.setCreator(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        orderItemDO.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        purchaseOrderService.createErpPurchaseOrderItem(orderItemDO);
    }

    private ErpPurchaseOrderDO processPurchaseOrder(Long supplierId, ErpPurchaseRequestItemsDO itemsDO, LocalDateTime currentTime) {
        ErpPurchaseOrderDO orderDO = new ErpPurchaseOrderDO();
        orderDO.setRequestId(itemsDO.getRequestId());
        orderDO.setNo(erpNoGenerator.generate(PURCHASE_ORDER_NO_PREFIX));
        orderDO.setStatus(ErpAuditStatus.PROCESS.getStatus());
//        orderDO.setOrderTime(itemsDO.getAdvicePurchaseDate().atStartOfDay());
        orderDO.setOrderTime(currentTime);
        orderDO.setTotalCount(itemsDO.getQty());
        orderDO.setTotalPrice(itemsDO.getAmount());
        orderDO.setTotalMaterialPrice(itemsDO.getAmount());
        orderDO.setTotalTaxPrice(itemsDO.getTaxAmount());
        orderDO.setInCount(BigDecimal.ZERO);
        orderDO.setReturnCount(BigDecimal.ZERO);
        orderDO.setSupplierId(supplierId);
        orderDO.setMerged(Boolean.FALSE);
        orderDO.setCreator(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        orderDO.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        purchaseOrderService.createErpPurchaseOrder(orderDO);
        return orderDO;
    }

    private Long processPurchaseRequest(BigDecimal purchasePrice, Integer approvalStatus) {
        ErpPurchaseRequestDO requestDO = new ErpPurchaseRequestDO();
        requestDO.setNo(erpNoGenerator.generate(PUR_REQUEST_NO_PREFIX));
        requestDO.setBizDate(LocalDateTime.now());
        requestDO.setTotalAmount(purchasePrice);
        requestDO.setIsUrgent(0);
        requestDO.setDemandType(ErpPurchaseDemandTypeEnum.RAW_MATERIAL_PROCUREMENT.getType());
        requestDO.setTotalTax(BigDecimal.ZERO);
        requestDO.setTotalTaxAmount(requestDO.getTotalAmount().add(requestDO.getTotalTax()));
        requestDO.setStatus(approvalStatus);
        requestDO.setCreator(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        requestDO.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        purchaseRequestService.savePurchaseRequest(requestDO);
        return requestDO.getId();
    }

    private ErpPurchaseRequestItemsDO processPurchaseRequestItems(boolean allowCreatePurchaseOrder, CreatePurchaseRequestFromMrpReq req, ErpMaterialInfoResp material, Long requestId, BigDecimal purchasePrice, Integer approvalStatus) {
        ErpPurchaseRequestItemsDO itemsDO = new ErpPurchaseRequestItemsDO();
        itemsDO.setRequestId(requestId);
        itemsDO.setMaterialId(req.getMaterialId());
        itemsDO.setUnitId(material.getUnitId());
        itemsDO.setQty(req.getActualQuantity());
        itemsDO.setPrice(material.getPurchasePrice());
        itemsDO.setTax(0D);
        itemsDO.setAmount(purchasePrice);
        itemsDO.setTaxAmount(
                itemsDO.getAmount().add(
                        itemsDO.getAmount().multiply(BigDecimal.valueOf(itemsDO.getTax()))
                )
        );
        itemsDO.setSourceDetailId(req.getId());
        itemsDO.setStatus(approvalStatus);
        itemsDO.setRequireDate(req.getStartDate());
        int harvestLeadTime = material.getRecevingLeadTime() != null ? material.getRecevingLeadTime() : HARVEST_LEAD_TIME;
        itemsDO.setAdvicePurchaseDate(req.getStartDate().minusDays(harvestLeadTime));
        BigDecimal purchaseOrderQty = BigDecimal.ZERO;
        if (allowCreatePurchaseOrder) {
            purchaseOrderQty = req.getActualQuantity();
        }
        itemsDO.setPurchaseOrderQty(purchaseOrderQty);
        itemsDO.setCreator(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        itemsDO.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
        purchaseRequestService.savePurchaseRequestItem(itemsDO);
        return itemsDO;
    }

    private Map<Long, ErpMaterialInfoResp> getMaterialMap(List<CreatePurchaseRequestFromMrpReq> list) {
        Set<Long> materialIds = list.stream().map(CreatePurchaseRequestFromMrpReq::getMaterialId).collect(Collectors.toSet());
        Map<Long, ErpMaterialInfoResp> materialMap = erpMaterialApi.getMaterialMap(materialIds);
        Assert.notNull(materialMap, "MRP下推采购订单失败，物料信息不正确");
        return materialMap;
    }

    private BigDecimal getPurchasePrice(BigDecimal materialPurchasePrice, BigDecimal actualQuantity) {
        return materialPurchasePrice != null ? materialPurchasePrice.multiply(actualQuantity) : BigDecimal.ZERO;
    }

    private ErpPurchaseConfig loadPurchaseConfig(boolean batchMode) {
        Map<String, ErpUserCustomizeConfigResp> configMap =
                erpUserCustomizeConfigApi.getUserCustomizeConfigMap(CONFIG_KEYS);
        return new ErpPurchaseConfig(configMap, batchMode);
    }

    private ErpPurchaseRequestItemsDO handlePurchaseRequest(ErpMaterialInfoResp material, ErpPurchaseConfig purchaseConfig, CreatePurchaseRequestFromMrpReq req) {
        BigDecimal purchasePrice = getPurchasePrice(material.getPurchasePrice(), req.getActualQuantity());
        Long requestId = processPurchaseRequest(purchasePrice, purchaseConfig.getApprovalStatus());
        return processPurchaseRequestItems(purchaseConfig.getAllowCreatePurchaseOrder(), req, material, requestId, purchasePrice, purchaseConfig.getApprovalStatus());
    }

    private CreatePurchaseOrderMergeEntryDTO buildCreatePurchaseOrderMergeEntryDTO(String purchaseOrderNo,
                                                                                   Long purchaseRequestId,
                                                                                   Long purchaseRequestItemId,
                                                                                   Long materialId,
                                                                                   BigDecimal qty){
        CreatePurchaseOrderMergeEntryDTO entryDTO = new CreatePurchaseOrderMergeEntryDTO();
        entryDTO.setPurchaseOrderNo(purchaseOrderNo);
        entryDTO.setPurchaseRequestId(purchaseRequestId);
        entryDTO.setPurchaseRequestItemId(purchaseRequestItemId);
        entryDTO.setMaterialId(materialId);
        entryDTO.setQty(qty);
        return entryDTO;
    }
}